package scanner;

import compiler.Properties;
//import compiler.SubCCompiler;

import parser.GrammarSymbols;
import util.Arquivo;

/**
 * Scanner class
 * 
 * @version 2010-september-04
 * @discipline Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class Scanner {

	// The file object that will be used to read the source code
	private Arquivo file;
	// The last char read from the source code
	private char currentChar;
	// The kind of the current token
	private int currentKind;
	// Buffer to append characters read from file
	private StringBuffer currentSpelling;
	// Current line and column in the source file
	private int line, column;

	/**
	 * Default constructor
	 */
	public Scanner() {
		this.file = new Arquivo(Properties.sourceCodeLocation);
		this.line = 0;
		this.column = 0;
		this.currentChar = this.file.readChar();
	}

	/**
	 * Returns the next token
	 * 
	 * @return
	 */
	public Token getNextToken() throws LexicalException {
		// Initializes the string buffer
		this.currentSpelling = new StringBuffer("");

		// Ignores separators
		while (isSeparator(this.currentChar)) {
			this.scanSeparator();
		}
		// Clears the string buffer
		this.currentSpelling = new StringBuffer();
		// Scans the next token
		this.currentKind = this.scanToken();
		// Creates and returns a token for the lexema identified
		return new Token(this.currentKind, this.currentSpelling.toString(),
				this.line, this.column);
	}

	/**
	 * Returns if a character is a separator
	 * 
	 * @param c
	 * @return
	 */
	private boolean isSeparator(char c) {
		if (c == '#' || c == ' ' || c == '\n' || c == '\t') {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Reads (and ignores) a separator
	 * 
	 * @throws LexicalException
	 * @StudentAuthor Fellipe Ten�rio < ftf@ecomp.poli.br >
	 * @StudentComments Tomei como base os slides da aula de analisador Lexico
	 */

	private void scanSeparator() throws LexicalException {
		// If it is a comment line
		if (this.currentChar == '#') {
			// Gets next char
			this.getNextChar();
			// Reads characters while they are graphics or '\t'
			// A command line should finish with a \n
			while (isGraphic(this.currentChar) || this.currentChar == '\t') {
				this.getNextChar();
			}
			if (this.currentChar == '\n') {
				getNextChar();
			} else {
				throw new LexicalException("Lexical Error", this.currentChar,
						this.line, this.column);
			}
		} else {
			this.getNextChar();
		}
	}

	/**
	 * Gets the next char
	 */
	private void getNextChar() {
		// Appends the current char to the string buffer
		this.currentSpelling.append(this.currentChar);
		// Reads the next one
		this.currentChar = this.file.readChar();
		// Increments the line and column
		this.incrementLineColumn();
	}

	/**
	 * Increments line and column
	 */
	private void incrementLineColumn() {
		// If the char read is a '\n', increments the line variable and assigns
		// 0 to the column
		if (this.currentChar == '\n') {
			this.line++;
			this.column = 0;
			// If the char read is not a '\n'
		} else {
			// If it is a '\t', increments the column by 4
			if (this.currentChar == '\t') {
				this.column = this.column + 4;
				// If it is not a '\t', increments the column by 1
			} else {
				this.column++;
			}
		}
	}

	/**
	 * Returns if a char is a digit (between 0 and 9)
	 * 
	 * @param c
	 * @return
	 */
	private boolean isDigit(char c) {
		if (c >= '0' && c <= '9') {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Returns if a char is a letter (between a and z or between A and Z)
	 * 
	 * @param Char
	 *            c
	 * @return
	 */
	private boolean isLetter(char c) {
		if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Returns if a char is a graphic (any ASCII visible character)
	 * 
	 * @param c
	 * @return
	 */
	private boolean isGraphic(char c) {
		if (c >= ' ' && c <= '~') {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Scans the next token Simulates the DFA that recognizes the language
	 * described by the lexical grammar
	 * 
	 * @return integer
	 * @throws LexicalException
	 */
	private int scanToken() throws LexicalException {
		/*
		 * TEM QUE FAZER O PARSER COM AUTOMATO USANDO SWITCH CASE!!!! TODO
		 */
		// The initial automata state is 0
		int state = 0;
		// While loop to simulate the automata
		while (true) {
			switch (state) {
			case 0:
				if (isDigit(currentChar)) {
					getNextChar();
					state = 1;
				} else if (isLetter(currentChar)) {
					getNextChar();
					state = 4;
				} else if (currentChar == ';') {
					getNextChar();
					state = 18;
				} else if (currentChar == '(') {
					getNextChar();
					state = 19;
				} else if (currentChar == ')') {
					getNextChar();
					state = 20;
				} else if (currentChar == '{') {
					getNextChar();
					state = 21;
				} else if (currentChar == '}') {
					getNextChar();
					state = 22;
				} else if (currentChar == '=') {
					getNextChar();
					state = 23;
				} else if (currentChar == '*') {
					getNextChar();
					state = 24;
				} else if (currentChar == '/') {
					getNextChar();
					state = 25;
				} else if (currentChar == '+') {
					getNextChar();
					state = 26;
				} else if (currentChar == '-') {
					getNextChar();
					state = 27;
				} else if (currentChar == '!') {
					getNextChar();
					state = 28;
				} else if (currentChar == '<') {
					getNextChar();
					state = 29;
				} else if (currentChar == '>') {
					getNextChar();
					state = 30;
				} else if (currentChar == ',') {
					getNextChar();
					state = 31;
				} else if (currentChar == '\000') {
					// getNextChar(); Has no other character after EOT!!!
					state = 998;
				} else {
					state = 999;
				}
				break;
			case 1:
				if (isDigit(currentChar)) {
					getNextChar();
					state = 1;
				} else if (currentChar == '.') {
					getNextChar();
					state = 2;
				} else {
					state = 35;
				}
				break;
			case 2:
				if (isDigit(currentChar)) {
					getNextChar();
					state = 3;
				} else {
					state = 999;
				}
				break;
			case 3:
				if (isDigit(currentChar)) {
					getNextChar();
					state = 3;
				} else {
					state = 35;
				}
				break;
			case 4:
				if (isLetter(currentChar) || isDigit(currentChar)) {
					getNextChar();
					state = 4;
				} else if (currentSpelling.toString().equals("void")) {
					// getNextChar();
					state = 5;
				} else if (currentSpelling.toString().equals("int")) {
					// getNextChar();
					state = 6;
				} else if (currentSpelling.toString().equals("double")) {
					// getNextChar();
					state = 7;
				} else if (currentSpelling.toString().equals("boolean")) {
					// getNextChar();
					state = 8;
				} else if (currentSpelling.toString().equals("if")) {
					// getNextChar();
					state = 9;
				} else if (currentSpelling.toString().equals("else")) {
					// getNextChar();
					state = 10;
				} else if (currentSpelling.toString().equals("while")) {
					// getNextChar();
					state = 11;
				} else if (currentSpelling.toString().equals("return")) {
					// getNextChar();
					state = 12;
				} else if (currentSpelling.toString().equals("break")) {
					// getNextChar();
					state = 13;
				} else if (currentSpelling.toString().equals("continue")) {
					// getNextChar();
					state = 14;
				} else if (currentSpelling.toString().equals("println")) {
					// getNextChar();
					state = 15;
				} else if (currentSpelling.toString().equals("false")) {
					// getNextChar();
					state = 16;
				} else if (currentSpelling.toString().equals("true")) {
					// getNextChar();
					state = 17;
				} else {
					return GrammarSymbols.ID;
				}
				break;
			case 5:
				return GrammarSymbols.VOID;
			case 6:
				return GrammarSymbols.INT;
			case 7:
				return GrammarSymbols.DOUBLE;
			case 8:
				return GrammarSymbols.BOOLEAN;
			case 9:
				return GrammarSymbols.IF;
			case 10:
				return GrammarSymbols.ELSE;
			case 11:
				return GrammarSymbols.WHILE;
			case 12:
				return GrammarSymbols.RETURN;
			case 13:
				return GrammarSymbols.BREAK;
			case 14:
				return GrammarSymbols.CONTINUE;
			case 15:
				return GrammarSymbols.PRINTLN;
			case 16:
				return GrammarSymbols.FALSE;
			case 17:
				return GrammarSymbols.TRUE;
			case 18:
				return GrammarSymbols.SEMICOLON;
			case 19:
				return GrammarSymbols.LPAR;
			case 20:
				return GrammarSymbols.RPAR;
			case 21:
				return GrammarSymbols.LBRACKET;
			case 22:
				return GrammarSymbols.RBRACKET;
			case 23: // =
				if (currentChar == '=') {
					getNextChar();
					state = 32;
				} else {
					return GrammarSymbols.ASSIGN;
				}
				break;
			case 24:
				return GrammarSymbols.MULT;
			case 25:
				return GrammarSymbols.DIV;
			case 26:
				return GrammarSymbols.PLUS;
			case 27:
				return GrammarSymbols.MINUS;
			case 28: // !
				if (currentChar == '=') {
					getNextChar();
					return GrammarSymbols.NOTEQUAL;
				} else {
					state = 999;
				}
				break;
			case 29: // <
				if (currentChar == '=') {
					getNextChar();
					state = 34;
				} else {
					return GrammarSymbols.LESSERTHAN;
				}
				break;
			case 30: // >
				if (currentChar == '=') {
					getNextChar();
					state = 33;
				} else {
					return GrammarSymbols.GREATERTHAN;
				}
				break;
			case 31:
				return GrammarSymbols.COMMA;
			case 32:
				return GrammarSymbols.EQUAL;
			case 33:
				return GrammarSymbols.GREATEREQUALTHAN;
			case 34:
				return GrammarSymbols.LESSEREQUALTHAN;
			case 35:
				return GrammarSymbols.NUMBER;
			case 998:
				return GrammarSymbols.EOT;
			case 999:
				throw new LexicalException("Unexpected Character!",
						currentChar, line, column);
			default:
				throw new LexicalException("Unexpected Character!",
						currentChar, line, column);
			}
		}
	}
}
